`timescale 1ns / 1ps
/******************************************************************************
*                                                                             *
* UTICA softcore v0.1                                                         *
*                                                                             *
* Copyright (c) 2012 Andrew D. Zonenberg                                      *
* All rights reserved.                                                        *
*                                                                             *
* Redistribution and use in source and binary forms, with or without modifi-  *
* cation, are permitted provided that the following conditions are met:       *
*                                                                             *
*    * Redistributions of source code must retain the above copyright notice  *
*      this list of conditions and the following disclaimer.                  *
*                                                                             *
*    * Redistributions in binary form must reproduce the above copyright      *
*      notice, this list of conditions and the following disclaimer in the    *
*      documentation and/or other materials provided with the distribution.   *
*                                                                             *
*    * Neither the name of the author nor the names of any contributors may be*
*      used to endorse or promote products derived from this software without *
*      specific prior written permission.                                     *
*                                                                             *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF        *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN     *
* NO EVENT SHALL THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT,         *
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT    *
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,   *
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY       *
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT         *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF    *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
*                                                                             *
******************************************************************************/

/**
	@file BoardHealthMonitor.v
	@author Andrew D. Zonenberg
	@brief Board health monitor
	
	Reads voltages from INA226 and (in v0.2) temperatures from a MCP9801.
 */
module BoardHealthMonitor(
	clk,
	i2c_sda, i2c_scl,
	vbus_3v3, vbus_2v5, vbus_1v2,
	ibus_3v3, ibus_2v5, ibus_1v2,
	pbus_3v3, pbus_2v5, pbus_1v2,
	update_interrupt
    );
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	// IO declarations
	input wire clk;
	
	//I2C bus
	output wire i2c_scl;
	inout wire i2c_sda;
	
	//Data outputs
	output reg[15:0] vbus_3v3 = 0;			//Power rail voltage (1.25 mV / bit)
	output reg[15:0] vbus_2v5 = 0;
	output reg[15:0] vbus_1v2 = 0;
	
	output reg[15:0] ibus_3v3 = 0;			//Power rail current (100uA/bit)
	output reg[15:0] ibus_2v5 = 0;
	output reg[15:0] ibus_1v2 = 0;
	
	output reg[15:0] pbus_3v3 = 0;			//Total power consumption (2.5mW/bit?)
	output reg[15:0] pbus_2v5 = 0;
	output reg[15:0] pbus_1v2 = 0;
		
	output reg update_interrupt = 0;			//Goes high for one clock cycle to indicate data has been updated
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	// The transceiver
	reg[15:0] clkdiv = 'd800;				//100 KHz @ 80 MHz
	
	reg tx_en = 0;
	reg[7:0] tx_data = 0;
	wire tx_ack;
	
	reg rx_en = 0;
	wire rx_rdy;
	wire[7:0] rx_out;
	reg rx_ack = 0;
	
	reg start_en = 0;
	reg restart_en = 0;
	reg stop_en = 0;
	
	wire busy;
	
	I2CTransceiver transceiver (
		.clk(clk), 
		.clkdiv(clkdiv), 
		.i2c_scl(i2c_scl), 
		.i2c_sda(i2c_sda), 
		.tx_en(tx_en), 
		.tx_ack(tx_ack), 
		.tx_data(tx_data), 
		.rx_en(rx_en), 
		.rx_rdy(rx_rdy), 
		.rx_out(rx_out), 
		.rx_ack(rx_ack), 
		.start_en(start_en), 
		.restart_en(restart_en), 
		.stop_en(stop_en),
		.busy(busy)
		);
		
	////////////////////////////////////////////////////////////////////////////////////////////////
	// Control code
	
	localparam STATE_IDLE			= 4'b0000;
	localparam STATE_INA_READ		= 4'b0001;
	localparam STATE_MCP_READ		= 4'b0010;
	localparam STATE_INA_INIT		= 4'b0011;
	localparam STATE_MCP_INIT		= 4'b0100;
	localparam STATE_POST_TIMER	= 4'b0101;
	localparam STATE_INA_READREG	= 4'b0110;
	
	reg[3:0] state = STATE_IDLE;
	
	reg[3:0] substate = 0;
	reg[3:0] return_substate = 0;
	
	localparam I2C_WRITE = 1'b0;
	localparam I2C_READ = 1'b1;
	
	localparam INA_REG_CONFIG	= 8'h00;
	localparam INA_REG_VSHUNT	= 8'h01;
	localparam INA_REG_VBUS		= 8'h02;
	localparam INA_REG_POWER	= 8'h03;
	localparam INA_REG_CURRENT	= 8'h04;
	localparam INA_REG_CALIB	= 8'h05;
	localparam INA_REG_MASK		= 8'h06;
	localparam INA_REG_ALERT	= 8'h07;
	localparam INA_REG_ID		= 8'hFF;
	
	//Bus address and index of the device of interest
	reg[6:0] addr = 0;
	reg[1:0] devnum = 3;
	
	reg[15:0] count = 1;
	
	reg initializing = 1;
	
	reg[7:0] ina_regid = 0;
	reg[15:0] ina_regval = 0;
	
	always @(posedge clk) begin
	
		tx_en <= 0;
		tx_data <= 0;
		
		rx_en <= 0;
		rx_ack <= 0;
	
		start_en <= 0;
		restart_en <= 0;
		stop_en <= 0;
		
		update_interrupt <= 0;
	
		case(state)
		
			//Wait for stuff to stabilize before we do anything
			STATE_POST_TIMER: begin
				count <= count + 16'd1;
				if(count == 0)
					state <= STATE_IDLE;
			end
		
			//Get ready to get a new reading
			//TODO: time delay between readings
			STATE_IDLE: begin
			
				//Go to next device
				/*
					Device map:
					Num		Addr			Desc
					0			7'b1000000	INA226, 3.3V rail
					1			7'b1001111	INA226, 2.5V rail
					2			7'b1001010	INA226, 1.2V rail
					3			7'b1001000	MCP9801 temp sensor (not present in v0.1 board revision)
				 */
				
				case(devnum)
					
					0: begin
						update_interrupt <= 1;
						devnum <= 1;
						addr <= 7'b1001111;
						substate <= 0;
						if(initializing)
							state <= STATE_INA_INIT;
						else
							state <= STATE_INA_READ;
					end
					
					1: begin
						devnum <= 2;
						addr <= 7'b1001010;
						substate <= 0;
						if(initializing)
							state <= STATE_INA_INIT;
						else
							state <= STATE_INA_READ;
					end
					
					2: begin
						devnum <= 3;
						addr <= 7'b1001000;
						substate <= 0;
						if(initializing)
							state <= STATE_MCP_INIT;
						else
							state <= STATE_MCP_READ;
						
						initializing <= 0;
					end
					
					3: begin
						devnum <= 0;
						addr <= 7'b1000000;
						substate <= 0;
						if(initializing)
							state <= STATE_INA_INIT;
						else
							state <= STATE_INA_READ;
					end
					
				endcase
			
			end	//end STATE_IDLE
			
			//Begin reading from an MCP9801
			//For v0.1 this chip isn't present due to a board bug; go on to next chip
			STATE_MCP_READ: begin
				state <= STATE_IDLE;
			end
			
			//Initialize an MCP9801
			//For v0.1 this chip isn't present due to a board bug; go on to next chip
			STATE_MCP_INIT: begin
				state <= STATE_IDLE;
			end
			
			//Initialize an INA226
			STATE_INA_INIT: begin
				
				case(substate)
					
					//Select the device
					0: begin
						if(!busy) begin
							start_en <= 1;
							substate <= 1;
						end
					end
					1: begin
						if(!busy) begin
							tx_data <= {addr, I2C_WRITE};
							tx_en <= 1;
							substate <= 2;
						end
					end
					
					//Write "calibration" register ID
					2: begin
						if(!busy) begin
							tx_data <= INA_REG_CALIB;
							tx_en <= 1;
							substate <= 3;
						end
					end
				
					//Write calibration value MSB first
					/*
						Current_LSB = 1024mA / 2^15
						= 0.00003125 A/bit = 31.25 uA/bit
						Simplify to 100uA/bit = 0.0001 A/bit = 0.1mA/bit
						
						Rshunt = 0.05 ohms
						
						CAL = 0.00512 / (0.0001 * 0.05)
						= 10,24
						= 0x0400
					 */
					3: begin
						if(!busy) begin
							tx_data <= 8'h04;
							tx_en <= 1;
							substate <= 4;
						end
					end
					4: begin
						if(!busy) begin
							tx_data <= 8'h00;
							tx_en <= 1;
							substate <= 5;
						end
					end
					
					//Finish up
					5: begin
						if(!busy) begin
							stop_en <= 1;
							substate <= 6;
						end
					end
					6: begin
						if(!busy) begin
							state <= STATE_IDLE;
						end
					end
					
					
				endcase
				
			end //end STATE_INA_INIT
			
			//Read data from an INA226
			STATE_INA_READ: begin
				
				case(substate)
									
					//Read bus voltage
					0: begin
						state <= STATE_INA_READREG;
						substate <= 0;
						ina_regid <= INA_REG_VBUS;
						return_substate <= 1;
					end
					
					//Update output registers
					1: begin
						case(devnum)
							0: vbus_3v3 <= ina_regval;
							1: vbus_2v5 <= ina_regval;
							2: vbus_1v2 <= ina_regval;
							default: begin
								//should never happen
							end
						endcase
							
						substate <= 2;
					end
					
					//Read current
					2: begin
						state <= STATE_INA_READREG;
						substate <= 0;
						ina_regid <= INA_REG_CURRENT;
						return_substate <= 3;
					end
					
					//Update output registers
					3: begin
						case(devnum)
							0: ibus_3v3 <= ina_regval;
							1: ibus_2v5 <= ina_regval;
							2: ibus_1v2 <= ina_regval;
							default: begin
								//should never happen
							end
						endcase
						
						substate <= 4;
					end
					
					//Read power
					4: begin
						state <= STATE_INA_READREG;
						substate <= 0;
						ina_regid <= INA_REG_POWER;
						return_substate <= 5;
					end
					
					//Update output registers
					5: begin
					
						case(devnum)
							0: pbus_3v3 <= ina_regval;
							1: pbus_2v5 <= ina_regval;
							2: pbus_1v2 <= ina_regval;
							default: begin
								//should never happen
							end
						endcase
							
						state <= STATE_IDLE;

						
					end
					
				endcase
				
			end
			
			//Read a register from an INA226
			//Subroutine called by STATE_INA_READ
			STATE_INA_READREG: begin
				
				case(substate)
					
					//Select the device
					0: begin
						if(!busy) begin
							start_en <= 1;
							substate <= 1;
						end
					end
					1: begin
						if(!busy) begin
							tx_data <= {addr, I2C_WRITE};
							tx_en <= 1;
							substate <= 2;
						end
					end
					
					//Select the register
					2: begin
						if(!busy) begin
							tx_data <= ina_regid;
							tx_en <= 1;
							substate <= 3;
						end
					end
					3: begin
						if(!busy) begin
							stop_en <= 1;
							substate <= 4;
						end
					end
					
					//Select device again, for reading this time
					4: begin
						if(!busy) begin
							start_en <= 1;
							substate <= 5;
						end
					end
					5: begin
						if(!busy) begin
							tx_data <= {addr, I2C_READ};
							tx_en <= 1;
							substate <= 6;
						end
					end
					
					//Read two bytes of data, ACK the first and NAK the second
					6: begin
						if(!busy) begin
							rx_en <= 1;
							rx_ack <= 1;
							substate <= 7;
						end
					end
					7: begin
						if(rx_rdy) begin
							ina_regval[15:8] <= rx_out;
							rx_en <= 1;
							rx_ack <= 0;
							substate <= 8;
						end
					end
					8: begin
						if(rx_rdy) begin
							stop_en <= 1;
							ina_regval[7:0] <= rx_out;
							substate <= 9;
						end
					end
					
					//Return to the calling state
					9: begin
						if(!busy) begin
							state <= STATE_INA_READ;
							substate <= return_substate;
						end
					end
					
				endcase
			end //end STATE_INA_READREG
			
		endcase
	
	end


endmodule
